package net.madarco.phpsense.util;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.dltk.core.IType;
import org.eclipse.dltk.core.ModelException;
import org.eclipse.php.internal.core.compiler.ast.nodes.Scalar;
import org.eclipse.php.internal.core.typeinference.PHPClassType;

import com.caucho.quercus.Quercus;
import com.caucho.quercus.env.ArrayValue;
import com.caucho.quercus.env.Env;
import com.caucho.quercus.env.JavaCollectionAdapter;
import com.caucho.quercus.env.Value;
import com.caucho.quercus.function.AbstractFunction;
import com.caucho.quercus.page.QuercusPage;
import com.caucho.vfs.Path;
import com.caucho.vfs.WriteStream;

/**
 * Manage all the stuffs related to the php interpreter.
 * 
 * @author Madarco
 *
 */
public class QuercusEnviroment {
	private static final int SCRIPT_TIME_LIMIT = 5000;
	
	private Quercus quercus;
	private Env php;
	private WriteStream out;

	private AbstractFunction findExpressionMethodCallType;
	private AbstractFunction findExpressionStaticMethodCallType;
	
	private final EclipseConsole console;
	private final String fileName;
	
	private boolean ready;

	private AbstractFunction getInstanceElementsCompletion;

	private AbstractFunction getClassElementsCompletion;

	private final boolean reloadAlways;

	/**
	 * Initialize the facade that handle the execution of the script.
	 * 
	 * @param fileName the php file to execute
	 * @param console the console where trace debug and error messages
	 * @throws IOException
	 */
	public QuercusEnviroment(String fileName, EclipseConsole console, boolean reloadAlways) {
		this.fileName = fileName;
		this.console = console;
		this.reloadAlways = reloadAlways;
		
		try {
			this.out = new WriteStream(new OutputStreamConsole(console.getConsoleOutputSteam()));
			this.out.setNewlineString("\n");
			this.out.setEncoding("iso-8859-1");
		    	
			this.quercus = new Quercus();
			this.quercus.init();
			
			this.init();
		}
		catch (Exception e) {
			console.error("Error during initializing the script "+this.fileName+": " + e);
		}
	}
	
	/**
	 * Reload the script
	 * 
	 * @throws IOException
	 */
	public void reload() {
		try {
			this.init();
		} catch (IOException e) {
			console.error("Error while reloading the script "+this.fileName+": " + e);
		}
	}
	
	/**
	 * Call the php findExpressionStaticMethodCallType function
	 * 
	 * @param receiverClassName
	 * @param methodName
	 * @param args
	 * @return the class name of the return type of the method
	 */
	public String callFindExpressionStaticMethodCallType(String receiverClassName, String methodName, List<Object> args) {
		if(!this.ready) return null;
		if(reloadAlways) this.reload();
		
		 Value ret = this.callFunction(findExpressionStaticMethodCallType, 		
				Quercus.objectToValue(receiverClassName),
				Quercus.objectToValue(methodName),
				createScalarsCollection(args));
		 if(ret.isString()) {
			 return ret.toJavaString();
		 }
		 return null;
	}
	
	/**
	 * Call the php findExpressionMethodCallType function
	 * 
	 * @param receiverType
	 * @param methodName
	 * @param args
	 * @return the class name of the return type of the method
	 */
	public String callFindExpressionMethodCallType(IType[] receiverType, String methodName, List<Object> args) {
		if(!this.ready) return null;
		if(reloadAlways) this.reload();
		
		Value ret = this.callFunction(findExpressionMethodCallType, 		
				createClassNamesCollection(receiverType),
				Quercus.objectToValue(methodName),
				createScalarsCollection(args));
		
		 if(ret.isString()) {
			 return ret.toJavaString();
		 }
		 return null;
	}
	
	public String[] callGetClassElementsCompletionDefined(IType[] receiverType) {
		if(!this.ready) return null;
		if(reloadAlways) this.reload();
		
		Value ret = this.callFunction(getClassElementsCompletion, 		
				createClassNamesCollection(receiverType));
		if (ret.isArray()) {
			ArrayValue array = (ArrayValue) ret;
			return (String[]) array.valuesToArray(this.php, String.class);
		}
		return new String[0];
	}
	
	public String[] callGetInstanceElementsCompletionDefined(IType[] receiverType) {
		if(!this.ready) return null;
		if(reloadAlways) this.reload();
		
		Value ret = this.callFunction(getInstanceElementsCompletion, 		
				createClassNamesCollection(receiverType));
		if (ret.isArray()) {
			ArrayValue array = (ArrayValue) ret;
			return (String[]) array.valuesToArray(this.php, String.class);
		}
		return new String[0];
	}
	
	/**
	 * Check if the findExpressionMethodCallType php function is defined
	 */
	public boolean isFindExpressionMethodCallTypeDefined() {
		return findExpressionMethodCallType != null;
	}

	/**
	 * Check if the findExpressionStaticMethodCallType php function is defined
	 */
	public boolean isFindExpressionStaticMethodCallTypeDefined() {
		return findExpressionStaticMethodCallType != null;
	}
	
	/**
	 * Check if the getInstanceMethodsCompletion php function is defined
	 */
	public boolean isGetInstanceElementsCompletionDefined() {
		return getInstanceElementsCompletion != null;
	}
	
	/**
	 * Check if the getClassMethodsCompletion php function is defined
	 */
	public boolean isGetClassElementsCompletionDefined() {
		return getClassElementsCompletion != null;
	}
	
	
	private void init() throws IOException {
		this.ready = false;
		this.findExpressionStaticMethodCallType = null;
		this.findExpressionMethodCallType = null;
		this.getInstanceElementsCompletion = null;
		this.php = null;
		
		Path filePath = quercus.getPwd().lookup(this.fileName);
		
		if(fileName == null) {
			console.error("Loading " + filePath);
		}
		else {
			QuercusPage page = quercus.parse(filePath);

			this.php = quercus.createEnv(page, this.out, null, null);
			this.php.setTimeLimit(SCRIPT_TIME_LIMIT);
			
			//Check registered function:
			this.findExpressionStaticMethodCallType = this.php.findFunction("findExpressionStaticMethodCallType");
			this.findExpressionMethodCallType = this.php.findFunction("findExpressionMethodCallType");
			this.getInstanceElementsCompletion = this.php.findFunction("getInstanceMethodsCompletion");
			this.getClassElementsCompletion = this.php.findFunction("getClassMethodsCompletion");
		}
		
		this.ready = true;
	}
	
	private Value callFunction(AbstractFunction func, Value... args) {
		try {
			php.resetTimeout();
			Value res = func.call(php, args);
			
			//if(log.isDebug()) {
				System.out.println("Returned: " + res.toDebugString());
			//}
			return res;
		}
		finally {
			try {
				out.flushBuffer();
			} catch (IOException e) {
				System.err.println(e);
				e.printStackTrace();
			}
		}

	}

	private JavaCollectionAdapter createScalarsCollection(List<?> values) {
		ArrayList<Object> ret = new ArrayList<Object>();
		for (Object obj : values) {
			//TODO: testare cosa succede quando chiamiamo un metodo con variabili come parametro:
			//Bar::init($foo);
			if(obj instanceof Scalar) {
				Scalar scalar = (Scalar) obj;
				
				if(Scalar.TYPE_STRING == scalar.getScalarType()) {
					ret.add((String)removeQuotes(scalar.getValue()));
				}
				else {
					ret.add((String)scalar.getValue());
				}
			}
		}
		return new JavaCollectionAdapter(php, ret, php.getJavaClassDefinition("java.lang.String"));
	}
	
	private JavaCollectionAdapter createClassNamesCollection(IType[] types) {
		ArrayList<Object> ret = new ArrayList<Object>();
		for (int i = 0; i < types.length; i++) {
			ret.add((String) PHPClassType.fromIType(types[i]).getTypeName());
			try {
				String[] superClasses = types[i].getSuperClasses();
				if(superClasses != null && superClasses.length > 0) {
					ret.addAll(Arrays.asList(superClasses));
				}
			} catch (ModelException e) {
				System.err.println(e);
				e.printStackTrace();
			}
		}
		return new JavaCollectionAdapter(php, ret, php.getJavaClassDefinition("java.lang.String"));
	}

	private static String removeQuotes(String str) {
		//Check for scalar constant like MyClass (not "MyClass"):
		if((str.charAt(0) == '\'' || str.charAt(0) == '"') 
			&& (str.charAt(str.length()-1) == '\'' || str.charAt(str.length()-1) == '"' )) {
			
			return str.substring(1, str.length() -1);
		}
		else {
			return str;
		}
	}
}
